package org.example.collection;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Console;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.springframework.util.Assert;

import java.util.*;

/**
 * Java jdk 原生  ArrayList 集合 API 测试
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2021/1/26 19:09
 */
@SuppressWarnings("all")
public class ArrayListTest {

    /**
     * ArrayList(Collection<? extends E> c)
     * list、Set 转 List
     */
    @Test
    public void set2List() {
        Set<String> set = new HashSet<>();
        set.add("法国");
        set.add("中国");
        set.add("美国");

        List<Object> list = new ArrayList<>(set);
        System.out.println(list);//[美国, 法国, 中国]
    }

    /**
     * removeAll(Collection<?> c)：从列表中移除所有包含在指定集合中的元素(可选操作)
     */
    @Test
    public void removeAll() {
        List<String> list = new ArrayList<>();
        list.add("10");
        list.add("20");
        list.add("30");
        list.add("20");
        list.add("40");

        List<String> list2 = new ArrayList<>();
        List<String> list3 = new ArrayList<>();

        list2.add("20");
        list2.add("402");
        list2.add("400");

        boolean removeAll1 = list.removeAll(list2);
        boolean removeAll2 = list.removeAll(list3);

        System.out.println(removeAll1);// true
        System.out.println(removeAll2);// false
        System.out.println(list);//[10, 30, 40]
        System.out.println(list2);//[20, 402, 400]
    }


    /**
     * boolean removeIf(Predicate<? super E> filter)
     * 删除此集合中满足给定条件的所有元素。迭代过程中或谓词抛出的错误或运行时异常会中继给调用者。
     * 如果删除了任何元素，则返回 true
     */
    @Test
    public void testRemoveIf1() {
        List<String> list = new ArrayList<>();
        list.add("10");
        list.add("20");
        list.add("30");
        list.add("20");
        list.add("40");

        list.removeIf(item -> item.equals("20"));
        // [10, 30, 40]
        System.out.println(list);

        list.removeIf(item -> item.compareTo("0") > 0);
        System.out.println(list);// []
    }

    /**
     * T[] toArray(T[] a)：list 转数组
     * 1、【强制】使用集合转数组的方法，必须使用集合的toArray(T[] array)，传入的是类型完全一致、长度为0的空数组.
     * 2、反例：直接使用 toArray 无参方法存在问题，此方法返回值只能是 Object[] 类，若强转其它类型数组将出现 ClassCastException 错误。
     * 3、使用 toArray 带参方法，数组空间大小的 length：
     * * 1）等于 0，动态创建与 size 相同的数组，性能最好。
     * * 2）大于 0 但小于 size，重新创建大小等于 size 的数组，增加 GC 负担。
     * * 3）等于 size，在高并发情况下，数组创建完成之后，size 正在变大的情况下，负面影响与2相同。
     * * 4）大于 size，空间浪费，且在 size 处插入 null 值，存在 NPE 隐患。
     */
    @Test
    public void toArrayTest1() {
        List<String> list = new ArrayList<>(2);
        list.add("好汉");
        list.add("HuXiao");
        list.add("110");
        list.add("120");
        String[] array = list.toArray(new String[0]);

        // [好汉, HuXiao, 110, 120]
        Console.log(array);
    }

    /**
     * 1、{@link Arrays#asList(java.lang.Object[])} 返回的是 Arrays 的内部类 {@link Arrays.ArrayList}，而并不是 java.util.ArrayList
     * 2、内部类继承关系：private static class ArrayList<E> extends AbstractList<E> extends AbstractCollection<E> implements List<E>
     * 3、Arrays 的内部类 {@link ArrayList} 只重写了部分方法，而  add、remove 方法是没有重写的，在 AbstractList 中直接抛的异常 throw new UnsupportedOperationException();
     * 4、asList 返回的集合如果调用 add、remove 等方法，就会抛出异常 UnsupportedOperationException - 不支持的操作
     */
    @Test
    public void asListTest1() {
        String[] array1 = new String[]{"中国", "美国", "英国", "法国"};
        List<String> list1 = Arrays.asList(array1);

        String[] array2 = list1.toArray(new String[list1.size()]);
        System.out.println(list1);//[中国, 美国, 英国, 法国]
        System.out.println(Arrays.asList(array2));//[中国, 美国, 英国, 法国]
    }

    /**
     * boolean retainAll(Collection<?> c)：求与集合 c 的交集。
     * 1、即凡是集合 c 中的元素，全部保留；方式集合c中没有的元素，全部舍弃。
     * 2、自己的内容会发生改变，集合 c 的内容不改变。
     */
    @Test
    public void retainAll() {
        List<String> list = new ArrayList<>();
        list.add("10");
        list.add("20");
        list.add("30");

        List<String> list2 = new ArrayList<>();
        list2.add("50");
        list2.add("20");
        list2.add("40");

        boolean retainAll = list.retainAll(list2);
        System.out.println(retainAll);// true
        System.out.println(list);//[20]
        System.out.println(list2);//[50, 20, 40]
    }

    /**
     * ArrayList 的 subList 方法注意事项:
     * 修改原集合元素的值，会影响子集合
     * 修改子集合元素的值，会影响原集合
     * 修改原集合的结构，当再访问子集合时，会引起 ConcurrentModificationException 异常.
     * 修改子集合的结构，当再访问原集合时，会引起 ConcurrentModificationException 异常.
     */
    @Test
    public void testSubList() {
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map1 = new HashMap<>();
        Map<String, Object> map2 = new HashMap<>();
        map1.put("code", 200);
        map2.put("code", 500);
        list.add(map1);
        list.add(map2);

        List<Map<String, Object>> subList = list.subList(0, 1);
        System.out.println(subList);//[{code=200}]

        // 修改原集合元素的值，会影响子集合
        list.get(0).put("msg", "success");
        System.out.println(subList);//[{msg=success, code=200}]

        // 修改子集合元素的值，会影响原集合
        subList.get(0).put("data", null);
        System.out.println(list);//[{msg=success, code=200, data=null}, {code=500}]

        // 修改原集合的结构，当再访问子集合时，会引起 ConcurrentModificationException 异常.
        // 修改子集合的结构，当再访问原集合时，会引起 ConcurrentModificationException 异常.
        list.add(map1);
        System.out.println(subList);
    }


    /**
     * List<List<T>> partition(List<T> list, int size)  通过传入分区长度，将指定列表分区为不同的块，每块区域的长度相同（最后一块可能小于长度）
     * 1、分区是在原List的基础上进行的，返回的分区是不可变的抽象列表，原列表元素变更，分区中元素也会变更。
     * 2、需要特别注意的是，此方法调用{@link List#subList(int, int)}切分List，
     * 3、这些第三方工具类的方法返回的是原List的视图，也就是说原List有变更，切分后的结果也会变更。
     */
    @Test
    public void testPartition1() {
        List<String> lists = Lists.newArrayList("1", "2", "3", "4", "5", "6", "7");
        List<List<String>> partition1 = org.apache.commons.collections4.ListUtils.partition(lists, 2);
        List<List<String>> partition2 = com.google.common.collect.Lists.partition(lists, 2);
        List<List<String>> partition3 = cn.hutool.core.collection.ListUtil.partition(lists, 2);

        System.out.println(partition1); // [[1, 2], [3, 4], [5, 6], [7]]
        System.out.println(partition2); // [[1, 2], [3, 4], [5, 6], [7]]
        System.out.println(partition3);// [[1, 2], [3, 4], [5, 6], [7]]

        // 下面这种是有问题的
        // 假如想要操作完第一组数据后，将第一组清空，想要继续操作后面的分组，而实际它已经影响了整个分组，导致整个数据分组已经发生了变化。
        for (int i = 0; i < partition1.size(); i++) {
            // 0===>[1, 2]===>[[1, 2], [3, 4], [5, 6], [7]]
            // 1===>[5, 6]===>[[3, 4], [5, 6], [7]]
            // 2===>[7]===>[[3, 4], [5, 6], [7]]
            System.out.println(i + "===>" + partition1.get(i) + "===>" + partition1);
            if (i == 0) {
                partition1.get(i).clear();
            }
        }
    }

    /**
     * 测试 原集合与返回的分段列表互不影响，原List有变更，切分后的结果不会变更，反之也是。
     */
    @Test
    public void testPartition2() {
        List<String> lists = Lists.newArrayList("1", "2", "3", "4", "5", "6", "7");
        System.out.println(ArrayListTest.partition(null, 2));// []
        System.out.println(ArrayListTest.partition(lists, 1));// [[1], [2], [3], [4], [5], [6], [7]]
        System.out.println(ArrayListTest.partition(lists, 2));// [[1, 2], [3, 4], [5, 6], [7]]
        System.out.println(ArrayListTest.partition(lists, 3));// [[1, 2, 3], [4, 5, 6], [7]]
        System.out.println(ArrayListTest.partition(lists, 4));// [[1, 2, 3, 4], [5, 6, 7]]
        System.out.println(ArrayListTest.partition(lists, 5));// [[1, 2, 3, 4, 5], [6, 7]]
        System.out.println(ArrayListTest.partition(lists, 6));// [[1, 2, 3, 4, 5, 6], [7]]
        System.out.println(ArrayListTest.partition(lists, 7));// [[1, 2, 3, 4, 5, 6, 7]]
        System.out.println(ArrayListTest.partition(lists, 8));// [[1, 2, 3, 4, 5, 6, 7]]

        System.out.println("=========================1=====================================");
        List<List<String>> partition1 = ArrayListTest.partition(lists, 2);
        for (int i = 0; i < partition1.size(); i++) {
            // 0===>[1, 2]===>[[1, 2], [3, 4], [5, 6], [7]]
            // 1===>[3, 4]===>[[], [3, 4], [5, 6], [7]]
            // 2===>[5, 6]===>[[], [3, 4], [5, 6], [7]]
            // 3===>[7]===>[[], [3, 4], [5, 6], [7]]
            System.out.println(i + "===>" + partition1.get(i) + "===>" + partition1);
            if (i == 0) {
                partition1.get(i).clear();
            }
        }
        System.out.println(lists);// [1, 2, 3, 4, 5, 6, 7]
        System.out.println("=========================2=====================================");
        List<String> lists2 = Lists.newArrayList("1", "2", "3", "4", "5", "6", "7");
        List<List<String>> partition2 = ArrayListTest.partition(lists2, 2);
        for (int i = 0; i < partition2.size(); i++) {
            // 0===>[1, 2]===>[[1, 2], [3, 4], [5, 6], [7]]
            // 1===>[3, 4]===>[[1, 2], [3, 4], [5, 6], [7]]
            // 2===>[5, 6]===>[[1, 2], [3, 4], [5, 6], [7]]
            // 3===>[7]===>[[1, 2], [3, 4], [5, 6], [7]]
            System.out.println(i + "===>" + partition2.get(i) + "===>" + partition2);
            if (i == 0) {
                lists2.add("8");
            }
            if (i == 1) {
                lists2.add(4, "9");
            }
            if (i == 2) {
                lists2.clear();
            }
        }
    }

    /**
     * 测试 原集合与返回的分段列表互不影响，原List有变更，切分后的结果不会变更，反之也是。
     */
    @Test
    public void testPartition3() {
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("1", "张三", 15, DateUtil.parse("1993/08/06")));
        personList.add(new Person("2", "李四", 45, DateUtil.parse("1996/07/25")));
        personList.add(new Person("3", "王五", 25, DateUtil.parse("1991/08/14")));
        personList.add(new Person("4", "马六", 35, DateUtil.parse("1998/05/20")));
        personList.add(new Person("5", "张七", 44, DateUtil.parse("1999/01/11")));

        List<List<Person>> partition = ArrayListTest.partition(personList, 2);
        for (int i = 0; i < partition.size(); i++) {
            // 0===>[Person{pid='1', pname='张三', age=15, birthday=1993-08-06 00:00:00}, Person{pid='2', pname='李四', age=45, birthday=1996-07-25 00:00:00}]===>[[Person{pid='1', pname='张三', age=15, birthday=1993-08-06 00:00:00}, Person{pid='2', pname='李四', age=45, birthday=1996-07-25 00:00:00}], [Person{pid='3', pname='王五', age=25, birthday=1991-08-14 00:00:00}, Person{pid='4', pname='马六', age=35, birthday=1998-05-20 00:00:00}], [Person{pid='5', pname='张七', age=44, birthday=1999-01-11 00:00:00}]]
            // 1===>[Person{pid='3', pname='王五', age=25, birthday=1991-08-14 00:00:00}, Person{pid='4', pname='马六', age=35, birthday=1998-05-20 00:00:00}]===>[[], [Person{pid='3', pname='王五', age=25, birthday=1991-08-14 00:00:00}, Person{pid='4', pname='马六', age=35, birthday=1998-05-20 00:00:00}], [Person{pid='5', pname='张七', age=44, birthday=1999-01-11 00:00:00}]]
            // 2===>[Person{pid='5', pname='张七', age=44, birthday=1999-01-11 00:00:00}]===>[[], [Person{pid='3', pname='王五', age=25, birthday=1991-08-14 00:00:00}, Person{pid='4', pname='马六', age=35, birthday=1998-05-20 00:00:00}], [Person{pid='5', pname='张七', age=44, birthday=1999-01-11 00:00:00}]]
            System.out.println(i + "===>" + partition.get(i) + "===>" + partition);
            if (i == 0) {
                partition.get(i).clear();
                personList.clear();
            }
        }
    }


    /**
     * 通过传入分区长度，将指定列表分区为不同的块，每块区域的长度相同（最后一块可能小于长度）<br>
     * 原集合与返回的分段列表互不影响，原List有变更，切分后的结果不会变更，反之也是。
     * 这里说的变更是指：集合新增了元素、减少了元素，或者直接清空了集合等等；
     * 如果集合元素本身是对象，比如 Java bean，或者 Map 对象等，当原集合修改这些元素对象的值时，分组列表中的元素值也是会变的，因为它们实际上引用的还是同一个对象。
     *
     * @param <T>  集合元素类型
     * @param list 列表，为空时返回 new ArrayList<>()
     * @param size 每个段的长度，当长度超过list长度时，size按照list长度计算，即只返回一个节点。必须大于0，否则报错。
     * @return 分段列表
     */
    public static <T> List<List<T>> partition(List<T> list, int size) {
        if (list == null || list.isEmpty()) {
            return new ArrayList<>();
        }
        Assert.isTrue(size > 0, "集合分组大小 Size 参数必须大于 0");
        List<List<T>> partitions = new ArrayList<>();
        List<T> partition = new ArrayList<>();
        for (int i = 0, listSize = list.size(); i < listSize; i++) {
            partition.add(list.get(i));
            if ((i + 1) % size == 0) {
                partitions.add(partition);
                partition = new ArrayList<>();
            }
        }
        if (partition.size() > 0) {
            partitions.add(partition);
        }
        return partitions;
    }


}
